package felix.things.router.opc;

import felix.things.router.opc.conf.mapping.AttributesMapping;
import felix.things.router.opc.conf.mapping.DeviceMapping;
import felix.things.router.opc.conf.mapping.KVMapping;
import felix.things.router.opc.conf.mapping.TimeseriesMapping;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;

import java.util.*;

@Data
@Slf4j
public class OpcUaDevice {

    private final String deviceName;
    private final DeviceMapping mapping;
    private final Map<String, NodeId> tagKeysMap = new HashMap<>();
    private final List<UaMonitoredItem> subscribeTags = new ArrayList<>();
    private final Map<NodeId, String> tagIdsMap = new HashMap<>();
    private final Map<String, Object> tagValues = new HashMap<>();
    private final Map<NodeId, Set<AttributesMapping>> attributesMap = new HashMap<>();
    private final Map<NodeId, Set<TimeseriesMapping>> timeseriesMap = new HashMap<>();

    private String deviceType;
    private long scanTs;

    public void unRegisterTags(Map<String, NodeId> newTagMap) {
        for (Map.Entry<String, NodeId> kv : newTagMap.entrySet()) {
            unRegisterTag(kv.getValue());
        }
    }

    public Map<String, NodeId> registerTags(Map<String, NodeId> newTagMap) {
        Map<String, NodeId> newTags = new HashMap<>();
        for (Map.Entry<String, NodeId> kv : newTagMap.entrySet()) {
            NodeId old = registerTag(kv);
            if (old == null) {
                newTags.put(kv.getKey(), kv.getValue());
            }
        }
        return newTags;
    }

    private void unRegisterTag(NodeId tagId) {
        attributesMap.remove(tagId);
        timeseriesMap.remove(tagId);
        tagIdsMap.remove(tagId);
    }

    private NodeId registerTag(Map.Entry<String, NodeId> kv) {
        String tag = kv.getKey();
        NodeId tagId = kv.getValue();
        // 如果mapping的设置为0 则全部添加
        mapping.getAttributes().stream()
                .filter(attr -> attr.getValue().contains(escape(tag)))
                .forEach(attr -> attributesMap.computeIfAbsent(tagId, key -> new HashSet<>()).add(attr));
        mapping.getTimeseries().stream()
                .filter(attr -> attr.getValue().contains(escape(tag)))
                .forEach(attr -> timeseriesMap.computeIfAbsent(tagId, key -> new HashSet<>()).add(attr));
        tagIdsMap.putIfAbsent(kv.getValue(), kv.getKey());
        return tagKeysMap.put(kv.getKey(), kv.getValue());
    }

//    @Deprecated
//    public void calculateDevice(String deviceSeparator, Integer deviceTypeIndex, String deviceNameIndex) {
//        String[] split = this.opcNode.getNodeId().getIdentifier().toString().split(deviceSeparator);
//        this.deviceType = split[deviceTypeIndex];
//        // TODO 此处构建设备名 X_XX_XXX_XX
//        List<Integer> ids = new ArrayList<>();
//        if (deviceNameIndex.contains(",")) {
//            ids = Arrays.stream(deviceNameIndex.split(",")).map(Integer::valueOf).collect(Collectors.toList());
//        } else {
//            ids.add(Integer.valueOf(deviceNameIndex));
//        }
//        this.deviceName = split[ids.get(0)];
//        for (int i = 1; i < ids.size(); i++) {
//            this.deviceName += ("_" + split[ids.get(i)]);
//        }
//    }

    public void updateTag(NodeId tagId, DataValue dataValue) {
        String tag = tagIdsMap.get(tagId);
        // TODO opcua 类型转换 目前都是String
        if (dataValue.getValue().isNotNull()) {
            if (dataValue.getValue().getValue().getClass().isArray()) {
                log.trace("type:{},value: {}", dataValue.getValue().getValue().getClass().getTypeName(), Arrays.toString((Object[]) dataValue.getValue().getValue()));
            } else {
                log.trace("type:{},value: {}", dataValue.getValue().getValue().getClass().getTypeName(), dataValue.getValue().getValue().toString());
            }
            tagValues.put(tag, dataValue.getValue().getValue());
        }
    }

    public void updateScanTs() {
        scanTs = System.currentTimeMillis();
    }

    private Set<AttributesMapping> getAttributesMapping(NodeId tag) {
        return attributesMap.getOrDefault(tag, Collections.emptySet());
    }

    private Set<TimeseriesMapping> getTimeseriesMapping(NodeId tag) {
        return timeseriesMap.getOrDefault(tag, Collections.emptySet());
    }

    private String escape(String tag) {
        return "${" + tag + "}";
    }

    public Map<String, Object> getAffectedAttributes(NodeId tagId, DataValue dataValue) {
        Set<AttributesMapping> attributes = getAttributesMapping(tagId);
        if (attributes.size() > 0) {
            return getKvEntries(attributes);
        } else {
            if (dataValue.getValue().isNotNull() && !timeseriesMap.containsKey(tagId)) {
                attributes = new HashSet<>();
                AttributesMapping mapping = new AttributesMapping();
                mapping.setKey(tagIdsMap.get(tagId));
                mapping.setType("String");
                mapping.setValue(escape(mapping.getKey()));
                attributes.add(mapping);
                return getKvEntries(attributes);
            } else {
                return Collections.emptyMap();
            }
        }
    }


    public Map<String, Object> getAffectedTimeseries(NodeId tagId, DataValue dataValue) {
        Set<TimeseriesMapping> timeseries = getTimeseriesMapping(tagId);
        if (timeseries.size() > 0) {
//            Date javaDate = dataValue.getSourceTime().getJavaDate();
            return getKvEntries(timeseries);
        } else {
            return Collections.emptyMap();
        }
    }

    public NodeId getTagNodeId(String tag) {
        return tagKeysMap.get(tag);
    }

    private Map<String, Object> getKvEntries(Set<? extends KVMapping> mappings) {
        Map<String, Object> result = new HashMap<>();
        for (KVMapping mapping : mappings) {
            String strVal = mapping.getValue();
            for (Map.Entry<String, Object> tagKV : tagValues.entrySet()) {
                if (tagKV.getValue().getClass().isArray()) {
                    strVal = strVal.replace(escape(tagKV.getKey()), Arrays.toString((Object[]) tagKV.getValue()));
                } else {
                    strVal = strVal.replace(escape(tagKV.getKey()), tagKV.getValue().toString());
                }
            }
            switch (mapping.getType()) {
                case "Boolean":
                    result.put(mapping.getKey(), Boolean.valueOf(strVal));
                    break;
                case "Double":
                    result.put(mapping.getKey(), Double.valueOf(strVal));
                    break;
                case "Long":
                    result.put(mapping.getKey(), Long.valueOf(strVal));
                    break;
                default:
                    result.put(mapping.getKey(), strVal);
                    break;
            }
        }
        return result;
    }

    public List<UaMonitoredItem> getSubscribeTags() {
        return subscribeTags;
    }
}
